上篇提到Saga模式.
末段的流程是把Saga的決策和執行順序的邏輯分佈在Saga的每一個參與者之中, 透過通訊交換事件的方式來溝通, 這種叫做Choreography-based saga(協同式Saga).
協同式Saga(Choreography-based saga)
Choreography是沒有一個集中協調器來各訴Saga參與方該做什麼事情, 而是Saga參與方對彼此的事件做出回應.
除了Saga之間直接透過API呼叫, 也能透過MQ做訊息投遞.
要做補償通知也是透過MQ做訊息投遞即可.
訊息傳遞
協同式Saga大部分實做都依賴MQ, 所以MQ的吞吐量和一些保證機制就非常重要了.
這部份後面講到NATS時會介紹.
這裡注意的是為了能讓Saga參與方能夠將接收到的每個事件映射到自己的資料上.
舉例: Debit服務上, 能紀錄是哪個Order來扣款的.
就必須能讓Saga參與方能夠查找到相對應的資料. 這裡會需要Saga發布方發布出去的資料需要包含關聯性ID這類的資訊, 以便其他參與方能夠方便查找.
pros
- 簡單: 各服務在對業務對象進行CRUD時做發布事件, 容易實做; 只要定義好資料結構, Emit到MQ即可; 接收方只要實做接收到命令的動作或是補償
- 鬆散耦合: 各Saga參與方所關心的事件與服務之間不會因此產生耦合.
cons
- 難以理解維護: 因為Saga的操作邏輯分散在每個服務內, 所以對系統不熟的同仁沒法快速理解是如何工作的; 且MQ要追蹤訊息也不容易, 也不太方便測試.
- 冪等性的設計考量
- 循環依賴的容易出現: 像上圖Order跟Account之間的第5-8步驟, 出現了循環依賴, 這是一種不好的設計風格
編排式Saga(Orchestration-based saga)
協同式Saga就像是個去中心化的概念, 現在要講的編排式Saga就是個中心化的服務來統一管理跟處理這些操作.
實做一個編排器(Orchestrator), 這個編排器是個業務Proxy類別, 唯一職責就是告訴Saga參與方該做什麼事情.
梳理一下流程, 將編排器視為一個狀態機(State Machine).
- 初始化訂單: 該Saga在等到Inventory驗證庫存
- 準備訂單: 該Saga收到Inventory回覆庫存Ok, 更改Order狀態, 並開始準備扣款
- Order Approved最終狀態: 收到Account的回應, 表示該Saga已經成功完成, 更新狀態
如果是庫存失敗
- 初始化訂單: 該Saga在等到Inventory驗證庫存
- Order Canceled最終狀態: 收到Inventory的回應, 表示該Saga被其中一個參與方拒絕
對於Order Saga來說, 操作就是對某Saga參與方的調用.
狀態之間的轉換, 由Order Saga執行的本地事務來觸發. 當前狀態跟本地事務的結果, 就決定了狀態轉換還有要執行的動作.
pros
- 相較簡單的依賴關係, 因為去除了循環依賴; 編排器會負責調用參與方, 但參與方並不會直接調用編排器.
- 改善關注點隔離, 簡化業務邏輯: 原本的Order類別需要去了解所有參與的Saga. 現在只要交給編排器就好
- 回滾補償更容易管理
- 方便測試
- 添加新步驟, 事務複雜度保持線性
cons
- 編排器存在集中過多的業務邏輯
事務隔離
SAGA由於沒支援隔離性, 這問題頗大條.
可能會發生,
- 更新丟失: 2個Saga同時操作一筆資料, 其中一個沒有讀取更新, 直接覆蓋蓋了另一個Saga所作的變更
- 髒讀: 1個Saga讀取了另一個Saga還沒完成的更新
- 模糊讀取/不可重複讀: 在一個Saga事務內, 其關心的資料被其他Saga進行了更動, 導致在此Saga中兩個不同步驟讀取相同的資料, 卻獲得不同的結果.
解法:
- 在應用層面加入邏輯鎖, 對這事務id上鎖, 確保操作串行化
- 使用樂觀鎖處理併發更新; 使用版本欄位來檢查當當前版本, 跟程式讀取聚合時的版本有沒有一致, 一致才更新
- 跟錢或是庫存有關的, 可以預先凍結資金或者是遇扣庫存的方式來隔離